/**
 * cap1128-input.c - Input device driver for Austria Microsystems' 
 *                   pmic CAP1128
 *
 * Copyright (C) 2009 Wolfson Microelectronics plc
 *
 * This file is subject to the terms and conditions of the GNU General
 * Public License. See the file "COPYING" in the main directory of this
 * archive for more details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
 
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>

#ifdef LOCAL_COMPILE
#include "cap1128.h"
#else
#include <linux/cap1128.h>
#endif

//const u8 weight[5] = {2,26,50,74,98};
const u8 weight[5] = {98,74,50,26,2};
static u8 cap1128_pos_cal(s8 * delta, u8 pad)
{
	u32 i;
	u32 sigma = 0;
	u32 deno = 0;
	u32 mask = 0;
	if (!pad)
		return 0;

	for(i=0; i<5; i++) {
		if(delta[i] > 4) {
			sigma += delta[i] * weight[i];
			deno += delta[i];
			pad |= 1<<(2+i);
		}
	}


	pad ^= (pad >> 1);
	for(i=0; i<5; i++) {
		pad >>= 1;
		if (pad & 1)
			mask++;
	}
	if (mask > 2) {
		sigma = 0;
	}
	else {
		sigma /= deno ? : 1;
	}

	return sigma;
}

inline void report_btn(struct input_dev *input, u8 pad)
{
	input_report_key(input, BTN_LEFT, pad & 0x40);
	input_report_key(input, BTN_MIDDLE, pad & 0x10);
	input_report_key(input, BTN_RIGHT, pad & 0x04);
}

static void cap1128_scan(struct work_struct *work)
{
	//static u32 key = 0;
	static int delta_pos = 0;
	//static u8 dir = 0;
	static u8 last_pos = 0;
	static u8 last_pad = 0;
	static u8 count = 0;
	//int ret;
	u8 pos, pad;
	u8 val = 0;
	s8 delta[5];
	struct cap1128_ts *ts = container_of(work,
						struct cap1128_ts, work.work);
	struct cap1128_dev *cap1128 = ts->dev;

	cap1128_bulk_read(cap1128, CAP1128_REG_SENSOR_DELTA+2, 5, delta);

	cap1128_bulk_read(cap1128, CAP1128_REG_BTN_STA, 1, &pad);
	//dev_info(cap1128->dev, "POS %d(%d, %d); PAD 0x%x\n", 
	//				pos, last_pos, delta_pos, pad);
	cap1128_write_bits(cap1128, CAP1128_REG_MAIN_CTRL, 1, 0);

#if 0
		dev_info(cap1128->dev, "delta %d,%d,%d,%d,%d\n",
				delta[4], delta[3], delta[2], delta[1], delta[0]);
#endif

	//pad &= 0xf8;
#if 0
	//pos &= 0x7F;
	if (!pad) {
	//if (0) {
		val = 0x80;
		cap1128_write(cap1128, CAP1128_REG_INT_ENABLE, 0x7e);
		//cap1128_write_bits(cap1128, CAP1128_REG_MAIN_CTRL, 1, 0);
		enable_irq(cap1128->irq);
	}
	else {
		dev_info(cap1128->dev, "delta %d,%d,%d,%d,%d; %d; 0x%x\n",
				delta[4], delta[3], delta[2], delta[1], delta[0],
				cap1128_pos_cal(delta, pad), pad);
		schedule_delayed_work(&ts->work, msecs_to_jiffies(70));
	}
#else
	pos = cap1128_pos_cal(delta, pad);
	if (!last_pos) last_pos = pos;
	if (!last_pad) last_pad = pad;

#if 0
	dev_info(cap1128->dev, "POS %d(%d, %d); PAD 0x%x(0x%x); CNT %d\n", 
					pos, last_pos, delta_pos, pad, last_pad, count);
#endif

	if (!pad) {
		if (last_pos) input_report_abs(ts->input, ABS_X, last_pos);
		input_report_key(ts->input, BTN_GEAR_UP, 0);
		input_report_key(ts->input, BTN_GEAR_DOWN, 0);
		//input_sync(ts->input);
		delta_pos  = 0;

		if (!delta_pos && count && (count < 5)) {
			report_btn(ts->input, last_pad);
			input_sync(ts->input);
		}
		input_report_key(ts->input, BTN_LEFT, 0);
		input_report_key(ts->input, BTN_MIDDLE, 0);
		input_report_key(ts->input, BTN_RIGHT, 0);
		input_sync(ts->input);

		count = 0;
		last_pos = 0;
		last_pad = 0;
		val = 0x80;
		//cap1128_bulk_write(cap1128, , 1, &val);
		cap1128_write(cap1128, CAP1128_REG_INT_ENABLE, 0x7e);
		//cap1128_write_bits(cap1128, CAP1128_REG_MAIN_CTRL, 1, 0);
		//cap1014_i2c_write_device(cap1014, CAP1014_REG_INT_ENABLE, 1, &val);
		enable_irq(cap1128->irq);
		return;
	}

	if (!delta_pos) {
		if ((count == 5) && (pad == last_pad)) {
			report_btn(ts->input, pad);
			input_report_abs(ts->input, ABS_X, last_pos);
		}
		//else if ((count == 7) && (pad != last_pad)) {
			//report_btn(ts->input, pad);
			//input_report_abs(ts->input, ABS_X, last_pos);
		//	count = 0;
		//}
		//goto out;		
	}

	if (pad == last_pad) {
		if (count < 6) count++;
	}
	else {
		count = 0;
	}

	if (pos) {
		if (last_pos - pos > 3) {
			if (delta_pos > 0) {
				delta_pos = -(last_pos - pos);
				input_report_abs(ts->input, ABS_X, pos);
				input_report_key(ts->input, BTN_GEAR_UP, 0);
			}
			delta_pos += pos - last_pos;
			if (delta_pos < -25) {
				count = 7;
				input_report_key(ts->input, BTN_GEAR_DOWN, 1);
				input_report_abs(ts->input, ABS_X, pos);
			}
		}
		else if (pos - last_pos > 3) {
			if (delta_pos < 0) {
				delta_pos = pos - last_pos;
				input_report_abs(ts->input, ABS_X, pos);
				input_report_key(ts->input, BTN_GEAR_DOWN, 0);
			}
			delta_pos += pos - last_pos;
			if  (delta_pos > 25) {
				count = 7;
				input_report_key(ts->input, BTN_GEAR_UP, 1);
				input_report_abs(ts->input, ABS_X, pos);
			}
		}
	}

	last_pos = pos;
out:

	last_pad = pad;
	input_sync(ts->input);
	schedule_delayed_work(&ts->work, msecs_to_jiffies(50));
#endif
}

static void irq_callback(int irq, void *data)
{
	struct cap1128_dev *cap1128 = data;
	//u8 val = 0;

	dev_dbg(cap1128->dev, "**********\n");
	
	disable_irq_nosync(cap1128->irq);

	cap1128_write(cap1128, CAP1128_REG_INT_ENABLE, 0x00); // Mask all interrupt

	//cap1128_write_bits(cap1128, CAP1128_REG_MAIN_CTRL, 1, 0);

	//schedule_delayed_work(&cap1014->work, msecs_to_jiffies(40)); //40 miliseconde
	schedule_delayed_work(&cap1128->ts->work, 3);

	return;
}

static int __devinit cap1128_ts_probe(struct platform_device *pdev)
{
	int ret;
	struct cap1128_dev *cap1128 = dev_get_drvdata(pdev->dev.parent);
	struct cap1128_ts *ts;

	ts = kzalloc(sizeof(struct cap1128_ts), GFP_KERNEL);
	if (!ts) {
		dev_err(&pdev->dev, "Can't allocate data\n");
		return -ENOMEM;
	}

	ts->dev = cap1128;
	INIT_DELAYED_WORK(&ts->work, cap1128_scan);

	ts->input = input_allocate_device();
	if (!ts->input) {
		dev_err(&pdev->dev, "Can't allocate input dev\n");
		ret = -ENOMEM;
		goto err;
	}

	ts->input->name = "cap1128_ts";
	ts->input->phys = "cap1128_ts/input0";
	ts->input->dev.parent = pdev->dev.parent;
	input_set_capability(ts->input, EV_ABS, ABS_X);
	input_set_capability(ts->input, EV_KEY, BTN_GEAR_DOWN);
	input_set_capability(ts->input, EV_KEY, BTN_GEAR_UP);
	input_set_capability(ts->input, EV_KEY, BTN_LEFT);
	input_set_capability(ts->input, EV_KEY, BTN_MIDDLE);
	input_set_capability(ts->input, EV_KEY, BTN_RIGHT);
	//__set_bit(EV_REP, ts->input->evbit);

	input_set_drvdata(ts->input, ts);

	ret = input_register_device(ts->input);
	if (ret) {
		dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
		goto err1;
	}
	if (!cap1128->irq_callback && !cap1128->ts) {
		cap1128_write_bits(cap1128, CAP1128_REG_MAIN_CTRL, 1, 0);
		cap1128->irq_callback = irq_callback;
		cap1128->ts = ts;
		
		enable_irq(cap1128->irq);


		platform_set_drvdata(pdev, ts);
//schedule_delayed_work(&ts->work, 0);
		return 0;
	}
	dev_err(&pdev->dev, "IRQ callback or ts controler is occupied!\n");

	input_unregister_device(ts->input);
err1:
	input_free_device(ts->input);
err:
	kfree(ts);
	return ret;
}

static int __devexit cap1128_ts_remove(struct platform_device *pdev)
{
	struct cap1128_ts *ts = platform_get_drvdata(pdev);

	disable_irq(ts->dev->irq);
	ts->dev->irq_callback = NULL;
	ts->dev->ts = NULL;

	cancel_delayed_work_sync(&ts->work);
	input_unregister_device(ts->input);
	input_free_device(ts->input);

	ts->dev->ts = NULL;
	return 0;
}

static struct platform_driver cap1128_ts_driver = {
	.probe		= cap1128_ts_probe,
	.remove		= __devexit_p(cap1128_ts_remove),
	.driver		= {
		.name	= "cap1128-ts",
		.owner	= THIS_MODULE,
	},
};

static int __init cap1128_ts_init(void)
{
	return platform_driver_register(&cap1128_ts_driver);
}
module_init(cap1128_ts_init);

static void __exit cap1128_ts_exit(void)
{
	platform_driver_unregister(&cap1128_ts_driver);
}
module_exit(cap1128_ts_exit);

MODULE_ALIAS("platform:cap1128-input");
MODULE_DESCRIPTION("cap1128 touch sensor");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Mingtao <mthuang@tdhiway.com>");

